//
//  Regex.swift
//  GerritHermes
//
//  Created by J.Zhou on 2020/1/20.
//  Copyright © 2020 J.Zhou. All rights reserved.
//

import Foundation

extension String {
    func grep(pattern: String) -> Regex.MatchResult {
        return self =~ Regex(pattern: pattern)
    }

    func replaceRegex(pattern: String, with replacement: String) -> String {
        let regexResult = self =~ Regex(pattern: pattern)
        return replace(regexResult: regexResult, template: replacement)
    }
}

struct Regex {
    struct MatchResult {
        var isMatched: Bool {
            return items.count > 0
        }

        let regex: NSRegularExpression
        let searchString: String
        let items: [NSTextCheckingResult]
        let captures: [String]

        init(regex: NSRegularExpression, search string: String, items: [NSTextCheckingResult]) {
            self.regex = regex
            searchString = string
            self.items = items

            captures = items.flatMap { result in
                (0..<result.numberOfRanges).map { i in
                    let nsrange = result.range(at: i)
                    return string.substring(with: nsrange)
                }
            }
        }

        subscript(i: Int) -> String {
            return captures[i]
        }
    }

    private let pattern: String
    private let nsRegex: NSRegularExpression

    init(pattern: String) {
        self.pattern = pattern

        let regex = try? NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options(rawValue: 0))
        if regex == nil {
            assertionFailure("Invalid regex: \(pattern)")
        }
        self.nsRegex = regex ?? NSRegularExpression()
    }

    func match(string: String) -> MatchResult {
        var matches = [NSTextCheckingResult]()
        let all = NSRange(location: 0, length: string.count)
        let moptions = NSRegularExpression.MatchingOptions(rawValue: 0)

        nsRegex.enumerateMatches(in: string, options:moptions, range:all) {
            (result: NSTextCheckingResult?, flags: NSRegularExpression.MatchingFlags, ptr: UnsafeMutablePointer<ObjCBool>) in

            if let result = result {
                matches.append(result)
            }
        }

        return MatchResult(regex: nsRegex, search: string, items: matches)
    }

    func replaceMatchesIn(string: String, with replacement:String) -> (replacements: Int, string: String) {
        let mutableString = NSMutableString(string:string)
        let replacements = nsRegex.replaceMatches(in: mutableString, options: NSRegularExpression.MatchingOptions(rawValue: 0), range:string.nsrange, withTemplate:replacement)

        return (replacements: replacements, string: String(mutableString))
    }

    func replaceMatchesIn(string: String, with replacement: String) -> String {
        return replace(regexResult: (string =~ self), template: replacement)
    }
}

infix operator =~

func =~ (searchString: String, regex: Regex) -> Regex.MatchResult {
    return regex.match(string: searchString)
}
private func replace(regexResult: Regex.MatchResult, template: String) -> String {
    let searchString = NSMutableString(string: regexResult.searchString)
    let fullRange = regexResult.searchString.nsrange
    regexResult.regex.replaceMatches(in: searchString, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: fullRange, withTemplate: template)
    return String(searchString)
}

private extension String {
    var nsrange: NSRange {
        return NSRange(location: 0, length: count)
    }

    func substring(with range: NSRange) -> String {
        return String(self[convertRange(nsrange: range)])
    }

    func convertRange(nsrange: NSRange) -> Range<String.Index> {
        let start = index(startIndex, offsetBy: nsrange.location)
        let end = index(start, offsetBy: nsrange.length)
        return start..<end
    }
}
